package org.ciallo.easytokencore.context.model;

import org.ciallo.easytokencore.error.EasyErrorCode;
import org.ciallo.easytokencore.exception.EasyTokenException;
import org.ciallo.easytokencore.util.VerificationUtils;

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Easy-Token Cookie包装类
 *
 * <p>核心功能：</p>
 * <ul>
 *   <li>封装HTTP Cookie的标准属性</li>
 *   <li>支持链式调用设置属性</li>
 *   <li>提供转换为Set-Cookie响应头的方法</li>
 *   <li>支持扩展自定义属性</li>
 * </ul>
 *
 * <p>主要属性：</p>
 * <ul>
 *   <li>name/value: Cookie名称和值</li>
 *   <li>maxAge: 有效期(秒)</li>
 *   <li>domain/path: 作用域和路径</li>
 *   <li>secure/httpOnly/sameSite: 安全相关属性</li>
 *   <li>extraAttrs: 扩展属性</li>
 * </ul>
 *
 * <p>使用示例：</p>
 * <pre>
 * {@code
 * EasyCookie cookie = new EasyCookie("token", "abc123")
 *     .setMaxAge(3600)
 *     .setPath("/")
 *     .setHttpOnly(true);
 * response.addHeader("Set-Cookie", cookie.toHeaderValue());
 * }
 * </pre>
 *
 * @Description: Easy-Token Cookie包装类
 * @Author: Jukomu
 * @Package: org.ciallo.easytokencore.context.model
 * @Project: easy-token-demo
 * @Version: 1.0.0
 * @name: EasyCookie
 * @Date: 2025-05-06
 * @Filename: EasyCookie
 * @see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">HTTP Cookies规范</a>
 */
public class EasyCookie {
    /**
     * 写入响应头时使用的key
     */
    public static final String HEADER_NAME = "Set-Cookie";

    /**
     * 名称
     */
    private String name;

    /**
     * 值
     */
    private String value;

    /**
     * 有效时长 （单位：秒），-1 代表为临时Cookie 浏览器关闭后自动删除
     */
    private int maxAge = -1;

    /**
     * 域
     */
    private String domain;

    /**
     * 路径
     */
    private String path;

    /**
     * 是否只在 https 协议下有效
     */
    private Boolean secure = false;

    /**
     * 是否禁止 js 操作 Cookie
     */
    private Boolean httpOnly = false;

    /**
     * 第三方限制级别（Strict=完全禁止，Lax=部分允许，None=不限制）
     */
    private String sameSite;

    // Cookie 属性参考文章：https://blog.csdn.net/fengbin2005/article/details/136544226

    /**
     * 额外扩展属性
     */
    private Map<String, String> extraAttrs = new LinkedHashMap<>();


    /**
     * 构造一个
     */
    public EasyCookie() {
    }

    /**
     * 构造一个
     *
     * @param name  名字
     * @param value 值
     */
    public EasyCookie(String name, String value) {
        this.name = name;
        this.value = value;
    }


    /**
     * @return 名称
     */
    public String getName() {
        return name;
    }

    /**
     * @param name 名称
     * @return 对象自身
     */
    public EasyCookie setName(String name) {
        this.name = name;
        return this;
    }

    /**
     * @return 值
     */
    public String getValue() {
        return value;
    }

    /**
     * @param value 值
     * @return 对象自身
     */
    public EasyCookie setValue(String value) {
        this.value = value;
        return this;
    }

    /**
     * @return 有效时长 （单位：秒），-1 代表为临时Cookie 浏览器关闭后自动删除
     */
    public int getMaxAge() {
        return maxAge;
    }

    /**
     * @param maxAge 有效时长 （单位：秒），-1 代表为临时Cookie 浏览器关闭后自动删除
     * @return 对象自身
     */
    public EasyCookie setMaxAge(int maxAge) {
        this.maxAge = maxAge;
        return this;
    }

    /**
     * @return 域
     */
    public String getDomain() {
        return domain;
    }

    /**
     * @param domain 域
     * @return 对象自身
     */
    public EasyCookie setDomain(String domain) {
        this.domain = domain;
        return this;
    }

    /**
     * @return 路径
     */
    public String getPath() {
        return path;
    }

    /**
     * @param path 路径
     * @return 对象自身
     */
    public EasyCookie setPath(String path) {
        this.path = path;
        return this;
    }

    /**
     * @return 是否只在 https 协议下有效
     */
    public Boolean getSecure() {
        return secure;
    }

    /**
     * @param secure 是否只在 https 协议下有效
     * @return 对象自身
     */
    public EasyCookie setSecure(Boolean secure) {
        this.secure = secure;
        return this;
    }

    /**
     * @return 是否禁止 js 操作 Cookie
     */
    public Boolean getHttpOnly() {
        return httpOnly;
    }

    /**
     * @param httpOnly 是否禁止 js 操作 Cookie
     * @return 对象自身
     */
    public EasyCookie setHttpOnly(Boolean httpOnly) {
        this.httpOnly = httpOnly;
        return this;
    }

    /**
     * @return 第三方限制级别（Strict=完全禁止，Lax=部分允许，None=不限制）
     */
    public String getSameSite() {
        return sameSite;
    }

    /**
     * @param sameSite 第三方限制级别（Strict=完全禁止，Lax=部分允许，None=不限制）
     * @return 对象自身
     */
    public EasyCookie setSameSite(String sameSite) {
        this.sameSite = sameSite;
        return this;
    }

    /**
     * @return 获取额外扩展属性
     */
    public Map<String, String> getExtraAttrs() {
        return extraAttrs;
    }

    /**
     * 写入额外扩展属性
     *
     * @param extraAttrs /
     * @return 对象自身
     */
    public EasyCookie setExtraAttrs(Map<String, String> extraAttrs) {
        this.extraAttrs = extraAttrs;
        return this;
    }

    /**
     * 追加扩展属性
     *
     * @param name  /
     * @param value /
     * @return 对象自身
     */
    public EasyCookie addExtraAttr(String name, String value) {
        if (extraAttrs == null) {
            extraAttrs = new LinkedHashMap<>();
        }
        this.extraAttrs.put(name, value);
        return this;
    }

    /**
     * 追加扩展属性
     *
     * @param name /
     * @return 对象自身
     */
    public EasyCookie addExtraAttr(String name) {
        return this.addExtraAttr(name, null);
    }

    /**
     * 移除指定扩展属性
     *
     * @param name /
     * @return 对象自身
     */
    public EasyCookie removeExtraAttr(String name) {
        if (extraAttrs != null) {
            this.extraAttrs.remove(name);
        }
        return this;
    }


    // toString
    @Override
    public String toString() {
        return "EasyCookie [" +
                "name=" + name +
                ", value=" + value +
                ", maxAge=" + maxAge +
                ", domain=" + domain +
                ", path=" + path
                + ", secure=" + secure +
                ", httpOnly=" + httpOnly +
                ", sameSite=" + sameSite +
                ", extraAttrs=" + extraAttrs +
                "]";
    }

    /**
     * 构建一下
     */
    public void builder() {
        if (path == null) {
            path = "/";
        }
    }

    /**
     * 转换为响应头 Set-Cookie 参数需要的值
     *
     * @return /
     */
    public String toHeaderValue() {
        this.builder();

        if (VerificationUtils.isNull(name)) {
            throw new EasyTokenException("name不能为空").setCode(EasyErrorCode.CODE_COOKIE_MISSING_NAME);
        }
        if (value != null && value.contains(";")) {
            throw new EasyTokenException("无效Value：" + value).setCode(EasyErrorCode.CODE_COOKIE_MISSING_VALUE);
        }

        // example：
        // Set-Cookie: name=value; Max-Age=100000; Expires=Tue, 05-Oct-2021 20:28:17 GMT; Domain=localhost; Path=/; Secure; HttpOnly; SameSite=Lax

        StringBuilder sb = new StringBuilder();
        sb.append(name).append("=").append(value);

        if (maxAge >= 0) {
            sb.append("; Max-Age=").append(maxAge);
            String expires;
            if (maxAge == 0) {
                expires = Instant.EPOCH.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME);
            } else {
                expires = OffsetDateTime.now().plusSeconds(maxAge).format(DateTimeFormatter.RFC_1123_DATE_TIME);
            }
            sb.append("; Expires=").append(expires);
        }
        if (!VerificationUtils.isNull(domain)) {
            sb.append("; Domain=").append(domain);
        }
        if (!VerificationUtils.isNull(path)) {
            sb.append("; Path=").append(path);
        }
        if (secure) {
            sb.append("; Secure");
        }
        if (httpOnly) {
            sb.append("; HttpOnly");
        }
        if (!VerificationUtils.isNull(sameSite)) {
            sb.append("; SameSite=").append(sameSite);
        }

        // 扩展属性
        if (extraAttrs != null) {
            extraAttrs.forEach((k, v) -> {
                if (VerificationUtils.isNull(v)) {
                    sb.append("; ").append(k);
                } else {
                    sb.append("; ").append(k).append("=").append(v);
                }
            });
        }

        return sb.toString();
    }
}
